<?php
error_reporting(E_ERROR | E_WARNING | E_PARSE);

function isAjax()
{
    return isset($_SERVER["HTTP_X_REQUESTED_WITH"]) && strtolower($_SERVER["HTTP_X_REQUESTED_WITH"]) == 'xmlhttprequest';
}

if (!isAjax() && empty($_GET) && strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') {
    Header("Location: index.html");
}

require_once __DIR__ . '/vendor/autoload.php';

define('ROOT_PATH', __DIR__);

use Crontab\Crontab;
use Crontab\CrontabFileHandler;
use Crontab\Job;
use Medoo\Medoo;

class Index
{

    /**
     * @var Medoo
     */
    private $_db = null;

    private $_db_file = '';

    private $_cron_path = '';

    public function __construct($db_file = '')
    {
        $this->_db_file = $db_file ?: ROOT_PATH . '/database.db';
        $this->_cron_path = ROOT_PATH . '/cron';
        !is_readable($this->_cron_path) && mkdir($this->_cron_path, 0755);
    }

    /**
     * @return Medoo
     */
    public function conn()
    {
        if (!$this->_db instanceof Medoo) {
            $this->_db = new Medoo([
                'database_type' => 'sqlite',
                'database_file' => $this->_db_file
            ]);
        }
        return $this->_db;
    }

    public function createTable()
    {
        $model = [
            'id'           => 'INTEGER PRIMARY KEY AUTOINCREMENT',
            'name'         => 'TEXT',
            'type'         => 'TEXT',
            'where1'       => 'TEXT',
            'where_hour'   => 'INTEGER',
            'where_minute' => 'INTEGER',
            'echo'         => 'TEXT',
            'hash'         => 'TEXT',
            'addtime'      => 'TEXT',
            'status'       => 'INTEGER DEFAULT 1',
            'save'         => 'INTEGER DEFAULT 3',
            'backupTo'     => 'TEXT DEFAULT off',
            'sName'        => 'TEXT',
            'sBody'        => 'TEXT',
            'sType'        => 'TEXT',
            'urladdress'   => 'TEXT',
        ];
        $table_name = 'crontab';
        $sql = "CREATE TABLE `{$table_name}` (";
        foreach ($model as $key => $item) {
            $sql .= "`{$key}` {$item},";
        }
        $sql = rtrim($sql, ',');
        $sql .= ");";
        return [$this->conn()->query($sql), $sql];
    }

    /**
     * 获取执行脚本
     * @param $param
     * @return mixed|string
     */
    public function getShell($param)
    {
        $head = "#!/bin/bash\nPATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin\nexport PATH\n";
        $shell = '';
        switch ($param['sType']) {
            case 'toUrl':
                $shell = $head . "curl -sS --connect-timeout 10 -m 3600 '{$param['urladdress']}'";
                break;
            case 'toShell':
                $shell = $head . preg_replace("/\r\n/", "\n", $param['sBody']);
                break;
        }
        if ($param['echo']) {
            $cron_name = $param['echo'];
        } else {
            $cron_name = md5(md5(microtime() . $shell));
        }
        $shell .= '
echo "----------------------------------------------------------------------------"
endDate=`date +"%Y-%m-%d %H:%M:%S"`
echo "★[$endDate] Successful"
echo "----------------------------------------------------------------------------"
';
        $file = $this->_cron_path . "/" . $cron_name;
        file_put_contents($file, $this->checkScript($shell));
        system('chmod 750 ' . $file);
        return $cron_name;
    }

    /**
     * 检查脚本
     * @param $shell
     * @return string|string[]|null
     */
    public function checkScript($shell)
    {
        $keys = ['shutdown', 'init 0', 'mkfs', 'passwd', 'chpasswd', '--stdin', 'mkfs.ext', 'mke2fs'];
        foreach ($keys as $key) {
            $shell = preg_replace("/{$key}/", "[***]", $shell);
        }
        return $shell;
    }

    public function setCronCycle(Job $job, $param)
    {
        switch ($param['type']) {
            case 'day':
                $job->setMinute($param['minute']);
                $job->setHour($param['hour']);
                break;
            case 'day-n':
                $job->setMinute($param['minute']);
                $job->setHour($param['hour']);
                $job->setDayOfMonth("*/{$param['where1']}");
                break;
            case 'hour':
                $job->setMinute($param['minute']);
                break;
            case 'hour-n':
                $job->setMinute($param['minute']);
                $job->setHour("*/{$param['where1']}");
                break;
            case 'minute-n':
                $job->setMinute("*/{$param['where1']}");
                break;
            case 'week':
                $job->setMinute($param['minute']);
                $job->setHour($param['hour']);
                $job->setDayOfWeek($param['where1']);
                break;
            case 'month':
                $job->setMinute($param['minute']);
                $job->setHour($param['hour']);
                $job->setDayOfMonth($param['where1']);
                break;
        }
        return $job;
    }

    /**
     * 添加计划任务
     * @throws Exception
     */
    public function add_crontab()
    {
        $param = $_POST;
        if (!isset($param['name']) || empty($param['name'])) {
            $this->error('任务名称不能为空');
        }
        $cron_name = $this->getShell($param);
        $cron_file = $this->_cron_path . '/' . $cron_name;
        $job = $this->setCronCycle(new Job(), $param);
        $job->setCommand($cron_file);
        $job->setLogFile($cron_file . '.log');
        $crontab = new Crontab();
        $crontab->addJob($job);
        $handler = new CrontabFileHandler();
        $res = $handler->write($crontab);
        if ($res->getError()) {
            $this->error($res->getError());
        }
        $data = [
            'name'         => $param['name'],
            'type'         => $param['type'],
            'where1'       => $param['where1'],
            'where_hour'   => $param['hour'],
            'where_minute' => $param['minute'],
            'echo'         => $cron_name,
            'hash'         => $job->getHash(),
            'addtime'      => date('Y-m-d H:i:s'),
            'status'       => 1,
            'save'         => $param['save'],
            'backupTo'     => $param['backupTo'],
            'sType'        => $param['sType'],
            'sName'        => $param['sName'],
            'sBody'        => $param['sBody'],
            'urladdress'   => $param['urladdress']
        ];
        $this->conn()->insert("crontab", $data);
        $this->success();
    }

    public function get_crontab_list()
    {
        $list = $this->conn()->select('crontab', '*');
        $crontab = new Crontab();
        $jobs = $crontab->getJobs();
        $ids = [];
        foreach ($list as $key => &$item) {
            if (!isset($jobs[$item['hash']]) || !file_exists($this->_cron_path . '/' . $item['echo'])) {
                $item['status'] = 0;
                $ids[] = $item['id'];
            }
        }
        if ($ids) {
            $this->conn()->update('crontab', ['status' => 0], ['id' => $ids]);
        }
        $this->success('', ['list' => $list, 'count' => count($list), 'total' => count($jobs)]);
    }

    public function get_logs()
    {
        $id = $_GET['id'];
        $cron = $this->conn()->get('crontab', ['addtime', 'echo', 'id', 'name'], ['id' => $id]);
        $file = $this->_cron_path . '/' . $cron['echo'] . '.log';
        if (!file_exists($file)) {
            $this->error('没有日志');
        }
        $cron['content'] = file_get_contents($file);
        $this->success('', $cron);
    }

    public function clear_logs()
    {
        $id = $_GET['id'];
        $cron = $this->conn()->get('crontab', ['addtime', 'echo', 'id', 'name'], ['id' => $id]);
        $file = $this->_cron_path . '/' . $cron['echo'] . '.log';
        if (file_exists($file)) {
            unlink($file);
        }
        $this->success();
    }

    /**
     * 删除任务
     * @throws Exception
     */
    public function del_crontab()
    {
        $ids = explode(',', $_GET['ids']);
        $crons = $this->conn()->select('crontab', ['addtime', 'echo', 'hash', 'id', 'name'], ['id' => $ids]);
        if (empty($crons)) {
            $this->error('没有要删除的任务');
        }
        $crontab = new Crontab();
        $cron_list = $crontab->getJobs();
        $write = false;
        foreach ($crons as $cron) {
            // 删除CRON
            if (isset($cron_list[$cron['hash']]) && $cron_list[$cron['hash']]) {
                $crontab->removeJob($cron_list[$cron['hash']]);
                $write = true;
            }
        }

        if ($write) {
            $result = $this->conn()->action(function ($database) use ($ids, $crontab) {
                // 删除数据库
                $this->conn()->delete('crontab', ['id' => $ids]);
                $handler = new CrontabFileHandler();
                $res = $handler->write($crontab);
                if ($res->getError()) {
                    return false;
                }
                return true;
            });
            if (!$result) {
                $this->error('操作失败');
            }
        } else {
            // 删除数据库
            $this->conn()->delete('crontab', ['id' => $ids]);
        }

        // 删除文件
        foreach ($crons as $cron) {
            $cron_file = $this->_cron_path . '/' . $cron['echo'];
            $cron_log = $cron_file . '.log';
            if (file_exists($cron_file)) {
                unlink($cron_file);
            }
            if (file_exists($cron_log)) {
                unlink($cron_log);
            }
        }

        $this->success('删除成功');
    }

    public function edit_crontab()
    {
        $id = $_GET['id'];
        $row = $this->conn()->get('crontab', '*', ['id' => $id]);
        if (empty($row)) {
            $this->error('任务不存在');
        }
        if ($this->isPost()) {
            $param = $_POST;
            $cron_name = $this->getShell($param);
            $cron_file = $this->_cron_path . '/' . $cron_name;
            $data = [
                'name'       => $param['name'],
                'type'       => $param['type'],
                'echo'       => $cron_name,
                'sBody'      => $param['sBody'],
                'urladdress' => $param['urladdress']
            ];
            $changeCycle = false;
            if ($param['type'] != $row['type'] || $param['where1'] != $row['where1']) {
                $data['where1'] = $param['where1'];
                $changeCycle = true;
            }
            if ($param['type'] != $row['type'] || $param['hour'] != $row['where_hour']) {
                $data['where_hour'] = $param['hour'];
                $changeCycle = true;
            }
            if ($param['type'] != $row['type'] || $param['minute'] != $row['where_minute']) {
                $data['where_minute'] = $param['minute'];
                $changeCycle = true;
            }
            if ($changeCycle) {
                $crontab = new Crontab();
                $list = $crontab->getJobs();
                if (isset($list[$row['hash']]) && $list[$row['hash']]) {
                    // 删除
                    $crontab->removeJob($list[$row['hash']]);
                    $handler = new CrontabFileHandler();
                    $res = $handler->write($crontab);
                    if ($res->getError()) {
                        $this->error($res->getError());
                    }
                    // 添加
                    $job = $this->setCronCycle(new Job(), $param);
                    $job->setCommand($cron_file);
                    $job->setLogFile($cron_file . '.log');
                    $crontab->addJob($job);
                    $res = $handler->write($crontab);
                    if ($res->getError()) {
                        $this->error($res->getError());
                    }
                    $data['hash'] = $job->getHash();
                }
            }
            $this->conn()->update('crontab', $data, ['id' => $id]);
            $this->success('修改成功');
        } else {
            $this->success('', $row);
        }
    }

    public function start_task()
    {
        $ids = explode(',', $_GET['ids']);
        $crons = $this->conn()->select('crontab', ['addtime', 'echo', 'hash', 'id', 'name'], ['id' => $ids]);
        if (empty($crons)) {
            $this->error('没有要删除的任务');
        }
        foreach ($crons as $cron) {
            $execstr = $this->_cron_path . '/' . $cron['echo'];
            system("nohup {$execstr} >> {$execstr}.log 2>&1 &");
        }
        $this->success('任务已执行');
    }

    public function set_status()
    {
        $id = $_GET['id'];
        $row = $this->conn()->get('crontab', '*', ['id' => $id]);
        if (!$row) {
            $this->error('任务不存在');
        }
        $crontab = new Crontab();
        $handler = new CrontabFileHandler();
        $list = $crontab->getJobs();
        $cron = isset($list[$row['hash']]) ? $list[$row['hash']] : null;
        if ($cron) {
            $crontab->removeJob($cron);
            $res = $handler->write($crontab);
            if ($res->getError()) {
                $this->error($res->getError());
            }
        }
        $data = ['status' => 0];
        if ($row['status'] == 0) {
            $data['status'] = 1;
            $row['hour'] = $row['where_hour'];
            $row['minute'] = $row['where_minute'];
            switch ($row['type']) {
                case 'minute-n':
                    $row['minute'] = $row['where1'];
                    break;
                case 'week':
                    $row['week'] = $row['where1'];
                    break;
                case 'hour-n':
                    $row['hour'] = $row['where1'];
                    break;
            }

            // 添加
            $cron_name = $this->getShell($row);
            $cron_file = $this->_cron_path . '/' . $cron_name;
            $job = $this->setCronCycle(new Job(), $row);
            $job->setCommand($cron_file);
            $job->setLogFile($cron_file . '.log');
            $crontab->addJob($job);
            $res = $handler->write($crontab);
            if ($res->getError()) {
                $this->error($res->getError());
            }
            $data['hash'] = $job->getHash();
        }
        $this->conn()->update('crontab', $data, ['id' => $id]);
        $this->success();
    }

    public function isPost()
    {
        return strtoupper($_SERVER['REQUEST_METHOD']) === 'POST';
    }

    public function isGet()
    {
        return strtoupper($_SERVER['REQUEST_METHOD']) === 'GET';
    }

    protected function success($msg = '', $data = null, $code = 0, $type = null, array $header = [])
    {
        $this->result($msg, $data, $code, $type, $header);
    }

    protected function error($msg = '', $data = null, $code = 1, $type = null, array $header = [])
    {
        $this->result($msg, $data, $code, $type, $header);
    }

    protected function result($msg, $data = null, $code = 0, $type = null, array $header = [])
    {
        header('Content-Type: application/json;charset=utf-8');
        $result = [
            'code' => $code,
            'msg'  => $msg,
            'time' => $_SERVER['REQUEST_TIME'],
            'data' => $data,
        ];
        die(json_encode($result, JSON_PRETTY_PRINT));
    }
}

if ($_GET['a']) {
    $index = new Index();
    if (!method_exists($index, $_GET['a'])) {
        die("操作方法{$_GET['a']}不存在");
    }
    $index->$_GET['a']();
}